[feat] roadmap 14 batch CN - Connect UX: state machine, disconnect, info drawer, invite ~srv - #40
Merged
Merged
Conversation
…nfo drawer, invite ~srv
The Connect pill becomes a three-state machine with professional session controls,
the always-on server indicator moves into an (i) info drawer, and invite links can
pin the signaling world.
- connectionState.js (NEW, store-only): sessionHost (set when OUR outbound request
is approved; null = hosting), peerJoinedAt, resetSession. Cleared per-peer in
handleDisconnected.
- Connect.svelte: state derives from OPENED peers (never userdata.length - the
roster fills at dial time). idle = dial input + blue Connect + (i); pending =
disabled input + AMBER Cancel + pulsing "Waiting for approval..." (aria-live);
connected = NO input, green dot + "Connected · <host> [+N]" + RED Disconnect.
data-state attr for e2e. copy() appends the invite ~srv param and guards the
"Generating..." pre-id state.
- peerApproval.cancelOutboundRequest: unwind a pending dial (queue entry, conn -
deleted BEFORE close so the onConnClose stale-guard and the restore retry loop
both stand down - whitelist, sessionHost). Also wired into the outbound-pending
toast (Cancel button).
- PeerConnection.leaveSession(): close every conn with EXPLICIT full teardown
(handleDisconnected + env-presets/hand-model drops - close events are often
never signaled), roster reset to self, queues cleared; NO peer.destroy() so the
invite id stays valid.
- ConnectInfoDrawer.svelte (NEW): Session (state badge, own id, host, per-peer
rtt/relay via the now-exported qColor + joined-ago, inline cancel) / Server
(resolved server, fallback warning, measured ping via the peerjs info endpoint,
discovery probe 200/401) / cloud drawerSlot mount (rooms settings land here in
batch RM). Outside-close via a WINDOW pointerdown listener - a fixed catcher
would size to the pill, not the viewport (.connect-wrap's translateX is the
containing block for fixed descendants; the CLAUDE.md transform gotcha).
- Invite ~srv (peerServer.js): inviteServerParam (empty for default resolution;
~srv=public on fallback/explicit-public-with-self-hosted; encoded host:port/path
for custom) + parseInviteHash/decodeInviteServer + applyInviteServerOverride
consulted FIRST in resolvePeerOptions/describePeerServer (session-only, never
falls back). Parsed at the TOP of the PeerConnection constructor (the
peer.on('open') hash flow runs too late for server selection); only the ~srv
tail is stripped so hash auto-connect is unchanged.
- FIX (latent): dialing while the signaling link is down threw inside
peer.connect() (undefined conn) and silently stranded the request - now guarded
in Connect + connectToPeer + restoreConnection with clear toasts.
- FIX: the Users peers-trigger showed a phantom peer while an outbound request was
pending (userdata gate) - now also gated on openedPeers.
- e2e: ui-polish I5 block migrated to the drawer (+ fallback badge/warning/outside
-close checks); NEW connect-states suite (19 checks: idle->pending->Cancel full
store unwind, no retry resurrection, ~srv encode/parse/decode round-trips,
copied link carries the param; two-peer connected/Disconnect block env-gated via
TWO_PEER=1).
svelte-check 498/76 (below the 499/77 baseline), build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the round (i) info button with a chevron disclosure that rotates 180deg on open, and make the info panel SLIDE down from under the pill instead of popping in: - Connect.svelte: cx-toggle chevron button (fa-chevron-down, rotates via .open), aria-expanded; keeps the id/testids + the amber fallback badge. - ConnectInfoDrawer.svelte: transition:slide (200ms cubicOut) on the panel — the translateX centering is preserved (slide animates height, not transform). - e2e: the outside-close check now polls past the slide-out transition (eventually). svelte-check 498/76, build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap #14 Batch CN — Connect UX (plan of record: cloud repo
docs/plans-core/roadmap-14-connect-ux-cloud.md).The pill is now a state machine
aria-live)Connected · <host> [+N]· red Disconnect · (i) — no dial inputState derives from opened connections (
openedPeers), neveruserdata.length— the roster fills optimistically at dial time (the phantom-peer trap; the Users peers-trigger had the same bug and is fixed in this PR too).data-stateon.connect-pillfor e2e.sessionHost(new store-onlyconnectionState.js): set when our outbound request is approved;null= you're hosting. Cleared on host disconnect / cancel / leave.peerApproval.cancelOutboundRequest): unwinds the queue entry, conn (deleted before close so theonConnClosestale-guard and the 4s restore-retry both stand down), whitelist, and host bookkeeping. Also added to the outbound-pending toast.PeerConnection.leaveSession()): closes every conn with explicit full teardown (close events are often never signaled — the P-A finding), resets the roster to self, clears both approval queues. Nopeer.destroy()— your invite id stays valid. Local scene kept.(i) info drawer (replaces the always-on server indicator)
Session (state badge · own id · host · per-peer RTT/relay + joined-ago · inline cancels) / Server (resolved server · fallback warning · measured ping to the peerjs info endpoint · discovery probe) / a new
drawerSlotcloud mount (room/host settings land here in batch RM). The (i) gains an amber badge whendidFallback. Outside-close uses a window pointerdown listener — a fixed catcher would size to the pill, not the viewport (.connect-wrap'stranslateXis the containing block for fixed descendants; the CLAUDE.md transform gotcha).Invite links pin the signaling world (
~srv)#A1B2C~srv=public/~srv=<encoded host[:port][path]>— appended by copy only when the resolved server differs from a fresh default build (fallback / explicit public / custom). Parsed at the top of thePeerConnectionconstructor (thepeer.on('open')hash flow runs too late for server selection); the override is consulted first inresolvePeerOptions/describePeerServer, session-only, and never falls back. Only the~srvtail is stripped, so hash auto-connect is unchanged.Latent bug fixed
Dialing while the signaling link was down threw inside
peer.connect()(undefined conn →conn.onTypeError) and silently stranded the request. Now guarded in Connect +connectToPeer+restoreConnectionwith clear toasts.Verification
connect-states.test.cjs(new) — 19/19 PASS: idle→pending→Cancel full store unwind, no retry resurrection after cancel,~srvencode/parse/decode round-trips, copied link carries the param. Two-peer connected/Disconnect block env-gated (TWO_PEER=1+ the hosts mapping).roadmap-13-ui-polish— I5 block migrated to the drawer (label, fallback flip, warning row, (i) badge, outside-close) — ALL PASS.svelte-check498/76 (below the 499/77 baseline) ·npm run buildgreen.🤖 Generated with Claude Code